跳到主要内容

Unity TileMap 插件的使用

Tilemap 工具的使用

这个是官方的瓷砖工具,主要用于画 2D的重复瓷砖之类的东西,按着 Shift 键就可以擦除,G 快速填充

image.png

注意,如果有多层的 Tilemap 且需要让某些层位于最前面(例如表现镜头前的物品)可以给这些 Tilemap 设置遮挡关系,如下图所示,这里角色(Robbie)在 Tilemap 下面,正常情况 Tilemap 是无法遮挡角色的,这时就可以设置这个 Shadows 的 Tilemap Renderer 的 SortingLayer 参数,使之始终位于最前面

image.png

绘制好瓷砖后,可以使用 Tilemap Collider 2D 组件给场景添加上碰撞器,但是注意,因为这个碰撞器有个精度的问题,会导致每个单独的碰撞器(一个格子一个)都可能卡住角色,所以需要勾选 Used By Composite 以及添加这个 Composite Collider 2D 组件(它会自动添加一个 Rigidbody 2D 物理,需要将它的 body type 设置成 static,不然整个场景都会往下掉),它能将这些单独的碰撞盒组成一个完整的碰撞盒

image.png

小技巧:Rigidbody 2D 有个碰撞检查(Collision Detection)默认是离散的(Discrete)如果将其设置成 连续的(Continuous)会使其移动、跳跃更加的顺畅,同时它还有个叫做 插值设置(Interpolate)将其改成 Interpolate,能避免落地时陷入地面再弹回来的状况

补充:如果想要更进一步设置瓷砖规则使其可以用于控制瓷砖的走向(不同的场景自动选择不同的瓷砖) 需要添加这个 Unity 2DExtras 它里面的 Terrain Tile 是一个生成地形的 Scriptable Tile;程序会根据 Tile 附近的 Grid,判断使用什么图像,令画面看得舒服; 使用教程

导入 Tile

首先点击 Sprite(贴图)设置这个 Sprite 类型为 Multiple,然后再设置一个格子所占的像素大小,如果是像素低的贴图还要设置这个 Filter Mode 为 Point模式(否则会变得模糊),最后别忘了点 Apply

然后创建一个画板,Create之后会自动创建一个绘图板,把素材拖进去,点击编辑

然后再设置这图如何切割,最后别忘了点 Apply

设置笔刷

如果想要像拉动 Tilemap 一样拉一排预制件,可以使用插件里面的这个 Prefab brush 笔刷

image.png

然后再在 Tile Palette 窗口选中刚才创建的笔刷(这个 SpikeBrush)

image.png

抗锯齿问题

如果出现这种割裂的情况需要去关闭项目的抗锯齿,这个抗锯齿是给 3D 游戏准备的,到了 2D会有一些问题

去到项目设置的 Quality 里将其关掉

Scriptable Tile 是什么

参考资料 官方文档 可编程瓦片 参考资料 Unity學習筆記#11 : 制作簡單Scriptable Tile

其实 Scriptable Tile 并不是什么特别的东西,当我们把 Sprite 拉到 Tile Palette 时,其实已经使用了 Unity 提供的 Scriptable Tile(就是 TileBase 的子类)来生成一个 TileAsset了

即当默认的 Tile 不能满足我们的需要时,就可以创造我们自己的 Scriptable Tile 来处理。

Scriptable Tile 需要继承 TileBase,由于 TileBase 继承于 ScriptableObject,所以其实Scriptable Tile也是Scriptable Object; 所以不会直接使用Scriptable Tile,而是使用Scriptable Tile生成出来的TileAsset; 所以Scriptable Tile不会像MonoBehaviour那样,可定义Update, Start 那些逻辑;

如果要从 BaseTile 开始继承,那必须自己实现这个 GetTileData 方法

这里看下 Tile 类的实现

public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
{
tileData.sprite = this.m_Sprite;
tileData.color = this.m_Color;
tileData.transform = this.m_Transform;
tileData.gameObject = this.m_InstancedGameObject;
tileData.flags = this.m_Flags;
tileData.colliderType = this.m_ColliderType;
}

自定义 Tile 获取信息

编写一个 Scriptable 用来返回

如果从 TileBase 构建一个 Tile 类那需要自己写的东西就多了去了,所以这里直接继承 Tile

[CreateAssetMenu(fileName = "New Tile", menuName = "My Tile/New Tile", order = 1)]
public class MyTile : Tile
{

}

动态生成 Tile

参考资料 unity2018使用tileMap生成地图 类似泰拉瑞亚创建和销毁地图块 参考资料 Unity 做法截取一张任意图片的 中心部分(代码实现)

因为需要使用 Json 动态生成地图,所以这里来研究下 TileMap 的使用

动态更换 Tile贴图

因为 Tile Map所用的贴图有 256 个不同的格子,一个个指定所用的格子是哪个肯定不现实,所用这里采用动态切图的方式来生成 Tile 的贴图(主要也是因为 Tile 支持动态更换 Sprites)

所以当前的思路就是使用预制 Tile 进去,再动态修改它的 Sprites

记得把这里设置成 Point 否则切割出来的图边缘会出现一条线(过滤的问题)

还要打开读写,否则 Build后会读取贴图失败

动态切割图片案例

这里先展示它的核心代码

public class GenerateTexture : MonoBehaviour
{
// 这里随意引用一个 ImageUI 来测试是否切割成功
public Image image;

void Start()
{
ChangeDrawTexture();
}

private void ChangeDrawTexture()
{
// 无需使用文件名后缀
Texture2D texture = Resources.Load<Texture2D>("Texture/Temptiles");
Debug.Log(texture);
image.sprite = BornSprite(texture, 16,16,2,1);
}


/// <summary>
/// 动态生成所需要的精灵,注意是以左下的 00 为起点,所以还需要转换坐标系
/// </summary>
/// <param name="texture">图源</param>
/// <param name="cols">一行有多少个格子</param>
/// <param name="rows">一列有多少个格子</param>
/// <param name="x">取 X 第几个(从 0 开始)</param>
/// <param name="y">取 Y 第几个(从 0 开始)</param>
/// <returns>切割好的 Sprite 对象</returns>
private static Sprite BornSprite(Texture2D texture,int cols,int rows, int x, int y)
{
int width = texture.width / cols;
int height = texture.height / rows;

// 因为 Unity 的坐标系与 HTML canvas 的坐标 Y轴是反的,所以这里要转换一下
// y 从零开始,所以 rows 要减一
y = (rows - 1) - y;

// 注意是以左下的 00 为起点
Sprite sp = Sprite.Create(texture,
// 参数分别为:切割的起点、切割的大小、中心点(这个直接 0.5、0.5 就行了)、最后一个参数是每单位的像素
new Rect(new Vector2(x * width, y * height),
new Vector2(width, height)),
new Vector2(0.5f, 0.5f)
32);
return sp;
}
}

读取 JSON 生成 TileMap

动态 TileMap 案例

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class CreateTileMap : MonoBehaviour
{

public Tilemap tileMap; //引用的 tileMap(就是在哪块画布上绘制)
public Tile baseTile; //使用的最基本的Tile,我这里是白色块,然后根据数据设置不同颜色生成不同Tile
Tile[] arrTiles;//生成的Tile数组

// Start is called before the first frame update
void Start()
{
StartCoroutine(InitData());
}

void Update()
{
//销毁墙体
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePosition = Input.mousePosition;
Vector3 wordPosition = Camera.main.ScreenToWorldPoint(mousePosition);
Vector3Int cellPosition = tileMap.WorldToCell(wordPosition);
//tilemap.SetTile(cellPosition, gameUI.GetSelectColor().colorData.mTile);
TileBase tb = tileMap.GetTile(cellPosition);
if (tb == null)
{
return;
}
//tb.hideFlags = HideFlags.None;
Debug.Log("鼠标坐标" + mousePosition + "世界" + wordPosition + "cell" + cellPosition + "tb" + tb.name);
//某个地方设置为空,就是把那个地方小格子销毁了
tileMap.SetTile(cellPosition, null);
//tilemap.RefreshAllTiles();
}

//空白地方创造墙体
if (Input.GetMouseButtonDown(1))
{
Vector3 mousePosition = Input.mousePosition;
Vector3 wordPosition = Camera.main.ScreenToWorldPoint(mousePosition);
Vector3Int cellPosition = tileMap.WorldToCell(wordPosition);
//tilemap.SetTile(cellPosition, gameUI.GetSelectColor().colorData.mTile);
TileBase tb = tileMap.GetTile(cellPosition);
if (tb != null)
{
return;
}

//格子填充
tileMap.SetTile(cellPosition, baseTile);
//tilemap.RefreshAllTiles();
}
}

/// <summary>
/// 地图生成 这里的 IEnumerator 表示这里是协程(不然会导致游戏卡住)
/// </summary>
/// <returns></returns>
IEnumerator InitData()
{
//大地图宽高
int levelW = 10;
int levelH = 10;

int colorCount = 6;

arrTiles = new Tile[colorCount];

for (int i = 0; i < colorCount; i++)
{
//想做生命墙,需要自己做个数据层,对应索引id就行
arrTiles[i] = ScriptableObject.CreateInstance<Tile>(); //创建Tile,注意,要使用这种方式
arrTiles[i].sprite = baseTile.sprite;
arrTiles[i].color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), 1);
}

for (int i = 0; i < levelH; i++)
{//这里就是设置每个Tile的信息了
for (int j = 0; j < levelW; j++)
{
tileMap.SetTile(new Vector3Int(j, i, 0), arrTiles[Random.Range(0, arrTiles.Length)]);
}
yield return null;
}

// 让 TileMap 逐渐变透明
while (true)
{
yield return new WaitForSeconds(2);
Color c = tileMap.color;//这里是改变 tileMap 的颜色,尝试是否可以整体变色
c.a -= Time.deltaTime;
tileMap.color = c;
}
}
}

打包时无法生成碰撞盒

参考资料 Pure TileMap Collider doesn't work at all on build

然后要设置 "Read/Write Enabled"

如果使用的是 WebGL 还是不行,则可以尝试换个浏览器,或者清除缓存